forked from
npmx.dev/npmx.dev
[READ-ONLY]
a fast, modern browser for the npm registry
1import * as v from 'valibot'
2import { PackageVersionQuerySchema } from '#shared/schemas/package'
3import type { PackageFileTreeResponse } from '#shared/types'
4import { CACHE_MAX_AGE_ONE_YEAR, ERROR_FILE_LIST_FETCH_FAILED } from '#shared/utils/constants'
5
6/**
7 * Returns the file tree for a package version.
8 *
9 * URL patterns:
10 * - /api/registry/files/packageName/v/1.2.3 - required version
11 * - /api/registry/files/@scope/packageName/v/1.2.3 - scoped package
12 */
13export default defineCachedEventHandler(
14 async event => {
15 // Parse package name and version from URL segments
16 // Patterns: [pkg, 'v', version] or [@scope, pkg, 'v', version]
17 const pkgParamSegments = getRouterParam(event, 'pkg')?.split('/') ?? []
18
19 const { rawPackageName, rawVersion } = parsePackageParams(pkgParamSegments)
20
21 try {
22 const { packageName, version } = v.parse(PackageVersionQuerySchema, {
23 packageName: rawPackageName,
24 version: rawVersion,
25 })
26
27 const jsDelivrData = await fetchFileTree(packageName, version)
28 const tree = convertToFileTree(jsDelivrData.files)
29
30 return {
31 package: packageName,
32 version,
33 default: jsDelivrData.default ?? undefined,
34 tree,
35 } satisfies PackageFileTreeResponse
36 } catch (error: unknown) {
37 handleApiError(error, {
38 statusCode: 502,
39 message: ERROR_FILE_LIST_FETCH_FAILED,
40 })
41 }
42 },
43 {
44 // Files for a specific version never change - cache permanently
45 maxAge: CACHE_MAX_AGE_ONE_YEAR, // 1 year
46 swr: true,
47 getKey: event => {
48 const pkg = getRouterParam(event, 'pkg') ?? ''
49 return `files:v1:${pkg.replace(/\/+$/, '').trim()}`
50 },
51 },
52)